#include "filestream.h"
#include "stringhandle.h"
namespace Moonlite {
namespace Data {
namespace Mpq
{
	TMPQFileStream::TMPQFileStream(TMPQArchive^ archive, System::String^ filename)
	{
		_handle = INVALID_HANDLE_VALUE;
		_filename = filename;
		_archive = archive;
		_cache = new std::vector<System::Byte>();
		Open();
	}

	TMPQFileStream::~TMPQFileStream()
	{
		Cleanup(true);
	}

	TMPQFileStream::!TMPQFileStream()
	{
		Cleanup(false);
	}

	void TMPQFileStream::Close()
	{
		Cleanup(true);
	}

	array<System::Byte>^ TMPQFileStream::Read(int size)
	{
		CheckBadState();
		int bytesToRead = _position + size > _length ? static_cast<int>(_length - _position) : size;
		if (bytesToRead < 0)
		{
			return nullptr;
		}

		array<System::Byte>^ buffer = gcnew array<System::Byte>(bytesToRead);
		if (bytesToRead == 0)
		{
			return buffer;
		}

		Read(buffer, 0, bytesToRead);
		return buffer;
	}

	array<System::Byte>^ TMPQFileStream::Read(int position, int size)
	{
		CheckBadState();
		Seek(position, System::IO::SeekOrigin::Begin);
		return Read(size);
	}

	int TMPQFileStream::Read(array<System::Byte>^ buffer, int index, int size)
	{
		CheckBadState();
		if (size <= 0)
		{
			return 0;
		}

		int bytesToRead = (_position + size > _length) ? static_cast<int>(_length - _position) : size;
		System::Runtime::InteropServices::Marshal::Copy(static_cast<System::IntPtr>(&((*_cache)[static_cast<int>(_position)])), buffer, index, bytesToRead);
		_position += bytesToRead;
		return bytesToRead;
	}

	void TMPQFileStream::Write(array<System::Byte>^ buffer, int index, int size)
	{
		UNREFERENCED_PARAMETER(buffer);
		UNREFERENCED_PARAMETER(index);
		UNREFERENCED_PARAMETER(size);
		CheckBadState();
		throw gcnew System::NotSupportedException();
	}

	__int64 TMPQFileStream::Seek(__int64 position, System::IO::SeekOrigin origin)
	{
		CheckBadState();
		switch (origin)
		{
			case System::IO::SeekOrigin::Begin:
			{
				_position = position;
				break;
			}

			case System::IO::SeekOrigin::Current:
			{
				_position += position;
				break;
			}

			case System::IO::SeekOrigin::End:
			{
				_position = _length + position;
				break;
			}
		}

		if (_position < 0)
		{
			_position = 0;
		}

		if (_position > _length)
		{
			_position = _length;
		}

		return _position;
	}

	void TMPQFileStream::SetLength(__int64 length)
	{
		UNREFERENCED_PARAMETER(length);
		CheckBadState();
		throw gcnew System::NotSupportedException();
	}

	void TMPQFileStream::Flush()
	{
		CheckBadState();
		throw gcnew System::NotSupportedException();
	}

	System::Boolean TMPQFileStream::CanSeek::get()
	{
		CheckBadState();
		return true;
	}

	System::Boolean TMPQFileStream::CanRead::get()
	{
		CheckBadState();
		return true;
	}

	System::Boolean TMPQFileStream::CanWrite::get()
	{
		CheckBadState();
		return false;
	}

	__int64 TMPQFileStream::Length::get()
	{
		CheckBadState();
		return _length;
	}

	__int64 TMPQFileStream::Position::get()
	{
		CheckBadState();
		return _position;
	}

	void TMPQFileStream::Position::set(__int64 position)
	{
		CheckBadState();
		Seek(position, System::IO::SeekOrigin::Begin);
	}

	long TMPQFileStream::CompressedLength::get()
	{
		CheckBadState();
		return SFileGetFileInfo(_handle, SFILE_INFO_COMPRESSED_SIZE, NULL, 0);
	}

	LCID TMPQFileStream::Locale::get()
	{
		CheckBadState();
		return SFileGetFileInfo(_handle, SFILE_INFO_LOCALEID, NULL, 0);
	}

	void TMPQFileStream::Locale::set(LCID locale)
	{
		CheckBadState();
		if (!SFileSetFileLocale(_handle, locale))
		{
			throw gcnew System::ArgumentException("Unable to change locale of file " + _filename + " to " + Locale + ".", "locale");
		}
	}

	HANDLE TMPQFileStream::Handle::get()
	{
		CheckBadState();
		return _handle;
	}

	System::String^ TMPQFileStream::FileName::get()
	{
		CheckBadState();
		return _filename;
	}

	TMPQArchive^ TMPQFileStream::Archive::get()
	{
		CheckBadState();
		return _archive;
	}

	System::Object^ TMPQFileStream::Tag::get()
	{
		return _tag;
	}

	void TMPQFileStream::Tag::set(System::Object^ tag)
	{
		_tag = tag;
	}

	System::Boolean TMPQFileStream::IsDisposed::get()
	{
		return _disposed;
	}

	void TMPQFileStream::Open()
	{
		int bytesRead = 0;
		pin_ptr<HANDLE> handlePtr = &_handle;
		TStringHandle filenameHandle(_filename);

		if (_archive == nullptr)
		{
			throw gcnew System::InvalidOperationException("The file stream has no associated archive.");
		}
		
		if (_archive->IsDisposed)
		{
			throw gcnew System::ObjectDisposedException(nullptr, "The archive of the file stream has been disposed.");
		}
			
		if ((_archive->Handle == NULL) || (_archive->Handle == INVALID_HANDLE_VALUE))
		{
			throw gcnew System::InvalidOperationException("The archive of the file stream has been closed.");
		}

		if (!_archive->Exists(_filename))
		{
			throw gcnew System::IO::FileNotFoundException("Could not find \"" + _filename + "\" in archive \"" + _archive->FileName + "\".", _filename);
		}
			
		if (!SFileOpenFileEx(_archive->Handle, filenameHandle.Value, SFILE_OPEN_FROM_MPQ, handlePtr))
		{
			throw gcnew System::IO::IOException("Unable to open file \"" + _filename + "\".");
		}

		_length = static_cast<int>(SFileGetFileInfo(_handle, SFILE_INFO_FILE_SIZE, NULL, 0));
		if (_length < 0)
		{
			_length = 0;
		}

		_cache->resize(static_cast<unsigned int>(_length));

		if (!SFileReadFile(_handle, &((*_cache)[0]), static_cast<DWORD>(_length), reinterpret_cast<LPDWORD>(&bytesRead), NULL))
		{
			throw gcnew System::IO::IOException("Read operation failed!");
		}

		if (_length != static_cast<long>(bytesRead))
		{
			throw gcnew System::IO::IOException("Read failed, expected " + _length + " bytes, read " + bytesRead + " bytes.");
		}

		_position = 0;
	}

	void TMPQFileStream::Cleanup(System::Boolean managed)
	{
		if (!_disposed)
		{
			UNREFERENCED_PARAMETER(managed);
			if(_handle != NULL)
			{
				SFileCloseFile(_handle);
				_handle = NULL;
			}

			if(_cache != NULL)
			{
				delete _cache;
				_cache = NULL;
			}

			_disposed = true;
		}
		else
		{
			throw gcnew System::ObjectDisposedException(nullptr);
		}
	}

	void TMPQFileStream::CheckBadState()
	{
		if (_disposed)
		{
			throw gcnew System::ObjectDisposedException(nullptr);
		}

		if ((_handle == NULL) || (_handle == INVALID_HANDLE_VALUE))
		{
			throw gcnew System::InvalidOperationException();
		}

		if (_archive->IsDisposed)
		{
			throw gcnew System::ObjectDisposedException(nullptr);
		}

		if ((_archive->Handle == NULL) || (_archive->Handle == INVALID_HANDLE_VALUE)){
			throw gcnew System::InvalidOperationException();
	}
}
}
}
}